#define CATCH_CONFIG_MAIN

#include <catch/catch.hpp>
#include <Frustum.hpp>
#include <DirectXMath.h>
#include <DirectXCollision.h>
#include <Aabbs.hpp>

#undef near
#undef far

bool operator == (DirectX::XMVECTOR lhs, DirectX::XMVECTOR rhs)
{
    return DirectX::XMVector4NearEqual(lhs, rhs, DirectX::g_XMEpsilon);
}

DirectX::XMVECTOR vec(float x, float y, float z, float w)
{
    return DirectX::XMVectorSet(x, y, z, w);
}

DirectX::XMVECTOR pos(float x, float y, float z)
{
    return DirectX::XMVectorSet(x, y, z, 1.0f);
}

DirectX::XMVECTOR dir(float x, float y, float z)
{
    return DirectX::XMVectorSet(x, y, z, 0.0f);
}

DirectX::XMVECTOR plane(DirectX::XMVECTOR pos, DirectX::XMVECTOR dir)
{
    return DirectX::XMPlaneNormalize(DirectX::XMPlaneFromPointNormal(pos, dir));
}

DirectX::XMVECTOR abs(DirectX::XMVECTOR vec)
{
    return DirectX::XMVectorAbs(vec);
}

TEST_CASE("SimpleFrusutmCtorFromMatrix", "[Frustum]")
{
    const auto fov = DirectX::XM_PIDIV4;
    const auto near = 1.0f;
    const auto far = 1000.0f;
    const auto proj = DirectX::XMMatrixPerspectiveFovLH(fov, 1.0f, near, far);
    BoundingFrustum frustum{ proj };
    const auto tanFovDiv2 = std::tan(fov / 2.0f);
    CHECK(Approx{ frustum.LeftSlope() } == -tanFovDiv2);
    CHECK(Approx{ frustum.Near() } == near);
    //CHECK(Approx{ frustum.Far() } == far);
    CHECK(Approx{ frustum.RightScope() } == tanFovDiv2);
    CHECK(Approx{ frustum.TopScope() } == tanFovDiv2);
    CHECK(Approx{ frustum.BottomScope() } == -tanFovDiv2);

    using namespace DirectX;

    std::array<XMVECTOR, 6> planes;
    frustum.GenPlanes(planes);

   // XMVECTOR rhs;
   // CHECK(XMPlaneEqual(planes[0], XMPlaneFromPointNormal(pos(0.0f, 0.0f, near), dir(0.0f, 0.0f, 1.0f))));
   // XMPlaneFromPointNormal(pos(0.0f, 0.0f, far), dir(0.0f, 0.0f, 1.0f));
   // CHECK(XMPlaneEqual(planes[1], rhs));*/
   // rhs = XMPlaneFromPointNormal(pos(0.0f, 0.0f, 0.0f), dir(1.0f, 0.0f, -tanFovDiv2));
   // CHECK(XMPlaneEqual(planes[2], rhs));

}

TEST_CASE("PlanesFromMatrix", "[Frustum]")
{
    const auto fov = DirectX::XM_PIDIV4;
    const auto near = 1.0f;
    const auto far = 1000.0f;
    const auto proj = DirectX::XMMatrixPerspectiveFovLH(fov, 1.0f, near, far);
    const auto tanFovDiv2 = std::tan(fov / 2.0f);

    std::array<DirectX::XMVECTOR, 6> planes;
    PlanesFromMatrix(proj, planes);
    using namespace DirectX;
    XMVECTOR rhs;
    rhs = plane(pos(0.0f, 0.0f, near), dir(0.0f, 0.0f, -1.0f));
    CHECK(abs(planes[0]) == abs(rhs));
   /* rhs = plane(pos(0.0f, 0.0f, far), dir(0.0f, 0.0f, -1.0f));
    CHECK(planes[1] == rhs);*/
    rhs = plane(pos(0.0f, 0.0f, 0.0f), dir(1.0f, 0.0f, tanFovDiv2));
    CHECK(abs(planes[2]) == abs(rhs));
    rhs = plane(pos(0.0f, 0.0f, 0.0f), dir(-1.0f, 0.0f, tanFovDiv2));
    CHECK(abs(planes[3]) == abs(rhs));

    const auto aabb1 = Aabb1{ -1.0f, -1.0f, 499.0f, 1.0f, 1.0f, 501.0f };
    CHECK(aabb1.IntersectWithPlane(planes[0]) == GeometryState::Outer);

    const auto aabb2 = Aabb1{ -1.0f, -1.0f, 999.0f, 1.0f, 1.0f, 1001.0f };
    CHECK(aabb2.IntersectWithPlane(planes[1]) == GeometryState::Intersect);
    CHECK(aabb2.IntersectWithPlane(planes[0]) == GeometryState::Outer);
}

//TEST_CASE("TrivialFrustumCtor", "[Frustum]")
//{
//    using namespace DirectX;
//    const auto fov = DirectX::XM_PIDIV4;
//    const auto near = 1.0f;
//    const auto far = 1000.0f;
//    ::BoundingFrustum frustum{ fov, 1.0f, near, far };
//    const auto tanFovDiv2 = std::tan(fov / 2.0f);
//    std::array<XMVECTOR, 6> planes;
//    frustum.GenPlanes(planes);
//    XMVECTOR rhs;
//    CHECK(XMPlaneEqual(planes[0], XMPlaneFromPointNormal(pos(0.0f, 0.0f, near), dir(0.0f, 0.0f, 1.0f))));
//    rhs = plane(pos(0.0f, 0.0f, far), dir(0.0f, 0.0f, 1.0f));
//    CHECK(XMPlaneEqual(planes[1], rhs));
//    rhs = plane(pos(0.0f, 0.0f, 0.0f), dir(1.0f, 0.0f, tanFovDiv2));
//    CHECK(XMPlaneEqual(planes[2], rhs));
//    rhs = plane(pos(0.0f, 0.0f, 0.0f), dir(1.0f, 0.0f, -tanFovDiv2));
//    CHECK(XMPlaneEqual(planes[3], rhs));
//
// 
//}
